package com.tommyb.util.gc
{
	/*************************************************************************
	 *
	 * http://code.google.com/p/flex-uicomponent-cleaner-and-gc-initiator/
	 *
	 * UIComponentCleaner class v1.0
	 * 
	 * The UIComponentCleaner class is a static utility class used to break 
	 * down a display component and its children so they will be prepared for
	 * garbage collection eligibility based on the Flash Player's simple
	 * reference count technique.  This class has been tested up to version
	 * 3.6 of the Adobe Flex SDK.  It has not been tested with versions 4.x
	 * of the SDK.
	 * 
	 * Use of this class to remove watchers from the passed 
	 * UIComponent-derived class instance requires modifying the Flex 
	 * framework-provided mx.binding::Watcher, mx.binding::PropertyWatcher,
	 * mx.binding::FunctionReturnWatcher and 
	 * mx.binding::RepeaterComponentWatcher classes to allow access to
	 * the following private properties declared in those classes:
	 * 
	 * mx.binding::FunctionReturnWatcher - Change the private "document", 
	 * "parameterFunction", "parentObj" and "functionGetter" properties to
	 * properties to public scope.
	 * 
	 * mx.binding::PropertyWatcher - Change the private "parentObj" Object and 
	 * protected "propertyGetter" Function properties to public scope.
	 * 
	 * mx.binding::RepeaterComponentWatcher - Change the private "clones"
	 * Array property to public scope.
	 * 
	 * mx.binding::Watcher - Change the protected "listeners" and "children" 
	 * Array properties to public scope.
	 * 
	 * If you are statically linking the Flex framework into your application,
	 * copy the above listed files from the Flex SDK source directory into
	 * your projects main source folder, maintaining the "mx.binding" package
	 * composition.  For example, if your main source folder is located in
	 * "src/main/flex", you would copy the files to the 
	 * "src/main/flex/mx/binding" folder before modifying them as instructed
	 * above.
	 * 
	 * If you are loading the Flex framework into your application using an
	 * RSL, you can follow the directions available at the below link to
	 * create your own RSL that can be loaded into your application domain
	 * before the Flex framework RSL in order for the modifications to be
	 * applied.
	 * 
	 * http://blogs.adobe.com/dloverin/2010/01/how_to_monkey_patch_when_using_flex_rsls.html
	 * 
	 * The UIComponentCleaner class will still function without failing if 
	 * you choose to not modify the Flex framework classes.  However, its
	 * level of success in preparing the passed UIComponent-derived instance
	 * for garbage collection will be reduced.
	 * 
	 *************************************************************************
	 * 
	 * Copyright (C) 2011 except where noted by Tommy Baggett 
	 * (http://www.linkedin.com/in/tommybaggett), Ansuria Solutions LLC
	 * 
	 * Permission is hereby granted, free of charge, to any person obtaining a
	 * copy of this software and associated documentation files (the 
	 * "Software"), to deal in the Software without restriction, including 
	 * without limitation the rights to use, copy, modify, merge, publish, 
	 * distribute, sublicense, and/or sell copies of the Software, and to 
	 * permit persons to whom the Software is furnished to do so, subject to 
	 * the following conditions:
	 * 
	 * The above copyright notice and this permission notice shall be included 
	 * in all copies or substantial portions of the Software.
	 * 
	 * The original copyright restrictions, as defined in the license 
	 * agreement that accompanied it, are respected on Adobe Systems 
	 * Incorporated code.
	 * 
	 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
	 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
	 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
	 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
	 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
	 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
	 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	 * 
	 *************************************************************************/

	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	
	import mx.binding.*;
	import mx.controls.Button;
	import mx.core.IFlexDisplayObject;
	import mx.core.IUIComponent;
	import mx.core.UIComponent;
	import mx.core.UITextField;
	import mx.core.mx_internal;
	import mx.states.State;

	use namespace mx_internal;

	public class UIComponentCleaner
	{
		/*************************************************************************
		 * The primary function of the UIComponentCleaner class is the 
		 * cleanDisplayComponents() function.  Once a display component is no 
		 * longer needed and should be released from memory, it should be passed 
		 * to this function.  Optionally, if you have a Flex mx::Container-derived
		 * class instance and you want to only remove its contents, you can use 
		 * this function and pass a value of true for the cleanChildrenOnly 
		 * parameter to remove all children and prepare them for the garbage 
		 * collection process without modifying the passed instance.
		 * 
		 * While the cleanDisplayComponents() function calls the other provided 
		 * functions to clean bindings, states, watchers, etc., from the passed 
		 * UIComponent-derived class instance and its children, you may also call 
		 * them directly if needed.
		 *************************************************************************/
		public static function cleanDisplayComponents( mainComponent : IUIComponent, cleanChildrenOnly : Boolean = false, cleanParentReferences : Boolean = true ):void
		{
			// If the main component contains child display components, we should drill down to the 
			// bottom-most children by calling ourselves recursively, then work our way back up the display list.
			if ( mainComponent is DisplayObjectContainer )
			{
				var container : DisplayObjectContainer = mainComponent as DisplayObjectContainer;
				var index : int = 0;
				while ( container && container.numChildren > index )
				{
					// Inspect each child display object.  If it is a UIComponent-derived class,
					// recursively call ourselves to drill into the originally-passed component's children.
					var childComponent : DisplayObject = container.getChildAt( index );
					if ( childComponent is UIComponent )
					{
						UIComponentCleaner.cleanDisplayComponents( childComponent as UIComponent, false, true );
					}

					try
					{
						// Avoid cleaning classes that won't function correctly afterwards
						if ( !( childComponent is UITextField ) )
						{
							container.removeChildAt( 0 );
						}
						else
						{
							// Increment index to avoid testing classes that won't function correctly when removed
							index++;
						}
					}
					catch ( e : Error )
					{
						// TODO:  Add error handling and/or logging as appropriate for your application
						trace( "Error in cleanDisplayComponents: " + e.message );
					}
				}
			}
			
			if ( !cleanChildrenOnly )
			{
				try
				{
					cleanComponentReferences( mainComponent as UIComponent, cleanParentReferences );
				}
				catch ( e : Error )
				{
					// TODO:  Add error handling and/or logging as appropriate for your application
					trace( "Error in cleanDisplayComponents: " + e.message );
				}
			}
		}
		
		/*************************************************************************
		 * The cleanComponentReferences() function calls other UIComponentCleaner
		 * static functions to remove the references from the passed 
		 * UIComponent-derived instance's parent document, bindings, states, 
		 * watchers, and both the document and owner properties.
		 *************************************************************************/
		public static function cleanComponentReferences( component : UIComponent, cleanParentReferences : Boolean ):void
		{
			if ( cleanParentReferences && component.id && component.parentDocument is IFlexDisplayObject && component.parentDocument.hasOwnProperty( component.id ) )
			{
				try
				{
					component.deleteReferenceOnParentDocument( IFlexDisplayObject( component.parentDocument ) );
				}
				catch( e : Error )
				{
					// TODO:  Add error handling and/or logging as appropriate for your application
					trace( "Error in cleanComponentReferences, UIComponent::deleteReferenceOnParentDocument() failed: " + e.message );
				}
			}
			
			cleanBindings( component );
			cleanStates( component );
			cleanWatchers( component );
			
			component.document = null;
			component.owner = null;
		}
		
		/*************************************************************************
		 * The cleanBindings() function iterates through the assigned bindings
		 * found on the passed component and removes them.  This function is based
		 * on the implementation of the Flex framework's 
		 * BindingManager::executeBindings() function.
		 *************************************************************************/
		public static function cleanBindings( component : Object ):void
		{
			if ( component )
			{
				var binding : Binding;
				var index : uint;
				var length : uint;
				
				// Release all bindings maintained by our component
				if ( component is IBindingClient || component.hasOwnProperty( "_bindings" ) )
				{
					while ( component._bindings.length > 0 )
					{
						binding = component._bindings.splice( 0, 1 )[0];
						cleanBinding( binding );
					}
					
					component._bindingsByDestination = {};
					component._bindingsBeginWithWord = {};
				}
				
				// Release parent document bindings to our component
				var componentID : String = component.id;
				if ( componentID != null )
				{
					var document : Object = component.descriptor && component.descriptor.document ? component.descriptor.document : component.parentDocument;
					var componentIDFW : String = getFirstWord( componentID );
					if ( document && ( document is IBindingClient || document.hasOwnProperty( "_bindingsByDestination" ) ) &&
						document._bindingsByDestination && document._bindingsBeginWithWord[componentIDFW] )
					{
						// Track the number of deletions to know when to stop iterating the bindings array
						var deletedCount : uint = 0;
						
						for ( var bindingID : String in document._bindingsByDestination )
						{
							if ( bindingID.charAt(0) == componentID.charAt(0) )
							{
								if ( bindingID.indexOf( componentID + "." ) == 0 ||
									bindingID.indexOf( componentID + "[" ) == 0 ||
									bindingID == componentID )
								{
									binding = document._bindingsByDestination[bindingID];
									if ( binding != null )
									{
										cleanBinding( binding );
										document._bindingsByDestination[bindingID] = null;
										delete document._bindingsByDestination[bindingID];
										
										deletedCount++;
									}
								}
							}
						}
						
						if ( deletedCount > 0 )
						{
							for ( index = 0, length = document._bindings.length; index < length && deletedCount > 0; index++ )
							{
								binding = document._bindings[ index ] as Binding;
								if ( binding != null && binding.document == null )
								{
									document._bindings.splice( index--, 1 );
									deletedCount--;
								}
							}
						}
					}
				}
			}
		}
		
		/*************************************************************************
		 * The cleanStates() function removes any states that are assigned to the
		 * passed UIComponent-derived instance and also clears their "target"
		 * property values.
		 *************************************************************************/
		static public function cleanStates( component : UIComponent ):void
		{
			try
			{
				// Iterate through the component's states and clean them up 
				// in preparation for garbage collection.
				while ( component.states && component.states.length > 0 )
				{
					var state : State = component.states.pop() as State;
					
					while ( state.overrides && state.overrides.length > 0 )
					{
						var override : Object = state.overrides.pop();
						
						if ( override.hasOwnProperty( "target" ) )
						{
							override.target = null;
						}
					}
				}
			}
			catch( e : Error )
			{
				// TODO:  Add error handling and/or logging as appropriate for your application
				trace( "Error in cleanStates: " + e.message );
			}
		}
		
		/*************************************************************************
		 * The cleanWatchers() function removes any watchers that are assigned to 
		 * the passed UIComponent-derived instance by the MXML compiler.  Note 
		 * that it does NOT remove ChangeWatcher instances that were manually
		 * added using the BindingUtils class.
		 *************************************************************************/
		static public function cleanWatchers( component : Object ):void
		{
			try
			{
				if ( component.hasOwnProperty( "_watchers" ) )
				{
					// Verify required Flex framework modifications have been made
					// before attempting to clean up watchers.  Note that this isn't
					// a thorough check as it only checks one modified property on
					// the base Watcher class.  Any error resulting from an incomplete
					// modification of the framework source will be caught by our
					// try/catch block.
					var watcher : Watcher = component._watchers[0];
					if ( !watcher || !watcher.hasOwnProperty( "listeners" ) )
					{
						return;
					}
						
					while ( component._watchers && component._watchers.length > 0 )
					{
						watcher = component._watchers.splice( 0, 1 )[0];
						if ( watcher )
						{
							cleanWatcher( watcher );
						}
					}
				}
			}
			catch( e : Error )
			{
				// TODO:  Add error handling and/or logging as appropriate for your application
				trace( "Error in cleanWatchers: " + e.message );
			}
		}
		
		/*************************************************************************
		 * The getFirstWord() function is a private helper for cleanBindings().
		 * Its functionality is duplicated from the Flex framework's 
		 * BindingManager class
		 *************************************************************************/
		////////////////////////////////////////////////////////////////////////////////
		//
		//  ADOBE SYSTEMS INCORPORATED
		//  This function copyright 2003-2007 Adobe Systems Incorporated
		//  All Rights Reserved.
		//
		//  NOTICE: Adobe permits you to use, modify, and distribute this function
		//  in accordance with the terms of the license agreement accompanying it.
		//
		////////////////////////////////////////////////////////////////////////////////
		static private function getFirstWord( destStr : String ):String
		{
			// indexPeriod and indexBracket will be equal only if they
			// both are -1.
			var indexPeriod : int = destStr.indexOf(".");
			var indexBracket : int = destStr.indexOf("[");
			if ( indexPeriod == indexBracket )
				return destStr;
			
			// Get the characters leading up to the first period or
			// bracket.
			var minIndex : int = Math.min( indexPeriod, indexBracket );
			if ( minIndex == -1 )
				minIndex = Math.max( indexPeriod, indexBracket );
			
			return destStr.substr( 0, minIndex );
		}
		
		/*************************************************************************
		 * The cleanBinding() function is a private helper for cleanBindings().
		 *************************************************************************/
		static private function cleanBinding( binding : Binding ):void
		{
			binding.document = null;
			binding.srcFunc = null;
			binding.destFunc = null;
			binding.destString = null;
			binding.twoWayCounterpart = null;
		}

		static private function cleanWatcher( watcher : Watcher ):void
		{
			// The Watcher instance is cast to an Object in this function to avoid any compiler 
			// complaints if the needed Flex framework modifications haven't been made.

			if ( watcher != null )
			{
				// Call ourself recursively to eliminate child watchers
				if ( Object(watcher).children != null )
				{
					while ( Object(watcher).children.length > 0 )
					{
						var childWatcher : Watcher = Object(watcher).children.splice(0, 1 )[0];
						if ( childWatcher != null )
						{
							cleanWatcher( childWatcher );
						}
					}
				}
				
				// Clear all associated bindings
				if ( Object(watcher).listeners != null )
				{
					while ( Object(watcher).listeners.length > 0 )
					{
						var binding : Binding = Object(watcher).listeners.splice(0, 1 )[0];
						if ( binding != null )
						{
							cleanBinding( binding );
						}
					}
				}
				
				watcher.value = null;
				
				if ( watcher is PropertyWatcher )
				{
					Object( watcher ).parentObj = null;
					Object( watcher ).propertyGetter = null;
				}
				
				if ( watcher is FunctionReturnWatcher )
				{
					Object( watcher ).parentObj = null;
					Object( watcher ).functionGetter = null;
					Object( watcher ).document = null;
					Object( watcher ).parameterFunction = null;
				}
				
				if ( watcher is RepeaterComponentWatcher && Object( watcher ).clones.length > 0 )
				{
					while ( Object(watcher).clones.length > 0 )
					{
						var childRepeaterWatcher : RepeaterComponentWatcher = Object(watcher).clones.splice( 0, 1 )[0];
						if ( childRepeaterWatcher != null )
						{
							cleanWatcher( childRepeaterWatcher );
						}
					} 
				}
			}
		}
	}
}